#include <v8.h>
#include <node.h>
#include <nan.h>
#include "AutoScaleSimulator.h"
#include "ValueFunctions.h"
#include "DurationFunctions.h"
#include <iostream>

#if _DEBUG
#define DEBUG_LOG(str) std::cout<<str
#else
#define DEBUG_LOG(str)
#endif
using namespace Nan;

class NodeSimulator : public Nan::ObjectWrap
{
public:
	static void Init(v8::Local<v8::Object> exports);

private:
	explicit NodeSimulator(int minInstance, int maxInstance);
	~NodeSimulator();

	static void New(const Nan::FunctionCallbackInfo<v8::Value>& info);
	static void SetLoadGenerator(const Nan::FunctionCallbackInfo<v8::Value>& info);
	static void AddRule(const Nan::FunctionCallbackInfo<v8::Value>& info);
	static void OneStep(const Nan::FunctionCallbackInfo<v8::Value>& info);
	static void IsFinished(const Nan::FunctionCallbackInfo<v8::Value>& info);
	static void GetServerStatus(const Nan::FunctionCallbackInfo<v8::Value>& info);
	static void GetLoadStatus(const Nan::FunctionCallbackInfo<v8::Value>& info);
	static Nan::Persistent<v8::Function> constructor;
	AutoScale::Simulator simulator_;
};

Nan::Persistent<v8::Function> NodeSimulator::constructor;

NodeSimulator::NodeSimulator(int minInstance, int maxInstance) : simulator_(minInstance, maxInstance) {
}

NodeSimulator::~NodeSimulator() {
}

void NodeSimulator::Init(v8::Local<v8::Object> exports) {
	Nan::HandleScope scope;

	// Prepare constructor template
	v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
	tpl->SetClassName(Nan::New("NodeSimulator").ToLocalChecked());
	tpl->InstanceTemplate()->SetInternalFieldCount(1);

	// Prototype
	Nan::SetPrototypeMethod(tpl, "setLoadGenerator", SetLoadGenerator);
	Nan::SetPrototypeMethod(tpl, "addRule", AddRule);
	Nan::SetPrototypeMethod(tpl, "oneStep", OneStep);
	Nan::SetPrototypeMethod(tpl, "isFinished", IsFinished);
	Nan::SetPrototypeMethod(tpl, "getServerStatus", GetServerStatus);
	Nan::SetPrototypeMethod(tpl, "getLoadStatus", GetLoadStatus);

	constructor.Reset(tpl->GetFunction());
	exports->Set(Nan::New("NodeSimulator").ToLocalChecked(), tpl->GetFunction());
}

void NodeSimulator::New(const Nan::FunctionCallbackInfo<v8::Value>& info) {
	if (info.Length() < 2)
	{
		Nan::ThrowError("Argument error");
		info.GetReturnValue().SetUndefined();
		return;
	}
	if (info.IsConstructCall()) {
		// Invoked as constructor: `new NodeSimulator(...)`

		int minInstance = info[0]->Int32Value();
		int maxInstance = info[1]->Int32Value();
		NodeSimulator* obj = new NodeSimulator(minInstance, maxInstance);
		obj->Wrap(info.This());
		info.GetReturnValue().Set(info.This());
	}
	else {
		// Invoked as plain function `NodeSimulator(...)`, turn into construct call.
		v8::Local<v8::Value> argv[] = { info[0], info[1] };
		v8::Local<v8::Function> cons = Nan::New<v8::Function>(constructor);
		info.GetReturnValue().Set(cons->NewInstance(2, argv));
	}
}

void NodeSimulator::OneStep(const Nan::FunctionCallbackInfo<v8::Value>& info) {
	NodeSimulator* obj = ObjectWrap::Unwrap<NodeSimulator>(info.Holder());
	DEBUG_LOG("before one step" << std::endl);
	obj->simulator_.OneStep();
	DEBUG_LOG("after one step" << std::endl);
	info.GetReturnValue().SetUndefined();
}

void NodeSimulator::IsFinished(const Nan::FunctionCallbackInfo<v8::Value>& info) {
	NodeSimulator* obj = ObjectWrap::Unwrap<NodeSimulator>(info.Holder());
	info.GetReturnValue().Set(obj->simulator_.Finished());
}

void NodeSimulator::SetLoadGenerator(const Nan::FunctionCallbackInfo<v8::Value>& info)
{
	Nan::HandleScope();
	if (info.Length() < 2 || !info[0]->IsNumber() || !info[1]->IsObject())
	{
		Nan::ThrowError("Argument error");
		info.GetReturnValue().SetUndefined();
		return;
	}
	int valueFuncType = info[0]->Int32Value();
	v8::Local<v8::Object> opts = info[1]->ToObject();
	std::function<int32_t(int32_t, int32_t)> valueFunc;
	std::function<int32_t(void)> durationFunc;
	int start = opts->Get(Nan::New<v8::String>("start").ToLocalChecked())->Int32Value();
	int end = opts->Get(Nan::New<v8::String>("end").ToLocalChecked())->Int32Value();
	int interval = opts->Get(Nan::New<v8::String>("interval").ToLocalChecked())->Int32Value();
	DEBUG_LOG("got start, end, interval" << std::endl);
	auto dukey = Nan::New<v8::String>("duration").ToLocalChecked();
	auto jitterKey = Nan::New<v8::String>("jitter").ToLocalChecked();
	if (!opts->Has(dukey))
	{
		Nan::ThrowError("Argument error");
		return;
	}
	if (opts->Has(jitterKey))
	{
		durationFunc = AutoScale::JitterDuration(opts->Get(dukey)->Int32Value(), opts->Get(jitterKey)->Int32Value());
	}
	else
	{
		durationFunc = AutoScale::ConstantDuration(opts->Get(dukey)->Int32Value());
	}
	DEBUG_LOG("got duration func" << std::endl);
	switch (valueFuncType)
	{
	case 0:
	{

		int value = opts->Get(Nan::New<v8::String>("value").ToLocalChecked())->Int32Value();
		DEBUG_LOG("set uniform generator, value:" << value << std::endl);
		valueFunc = AutoScale::Uniform(value);
		break;
	}
	case 1:
	{
		int peak = opts->Get(Nan::New<v8::String>("peak").ToLocalChecked())->Int32Value();
		int sigma = opts->Get(Nan::New<v8::String>("sigma").ToLocalChecked())->Int32Value();
		DEBUG_LOG("set normal generator, peak:" << peak << ", sigma:" << sigma << std::endl);
		valueFunc = AutoScale::Normal(peak, sigma);
		break;
	}
	case 2:
	{
		int peak = opts->Get(Nan::New<v8::String>("peak").ToLocalChecked())->Int32Value();
		int sigma = opts->Get(Nan::New<v8::String>("sigma").ToLocalChecked())->Int32Value();
		DEBUG_LOG("set exp generator, peak:" << peak << ", sigma:" << sigma << std::endl);
		valueFunc = AutoScale::Exponential(peak, sigma);
		break;
	}
	case 3:
	{
		DEBUG_LOG("set key path generator" << std::endl);
		AutoScale::KeyPath keyPath;
		auto keyPathArr = opts->Get(Nan::New<v8::String>("keyPath").ToLocalChecked()).As<v8::Array>();
		for (size_t i = 0; i < keyPathArr->Length(); ++i)
		{
			auto point = keyPathArr->Get(i).As<v8::Array>();
			keyPath.AddKeyPoint(point->Get(0)->Int32Value(), point->Get(1)->Int32Value());
		}
		valueFunc = keyPath;
		break;
	}
	default:
		Nan::ThrowError("Argument error");
		return;
	}
	NodeSimulator* obj = ObjectWrap::Unwrap<NodeSimulator>(info.Holder());
	DEBUG_LOG("set generator" << std::endl);
	obj->simulator_.SetLoadGenerator(AutoScale::LoadGenerator(start, end, interval, std::move(valueFunc), std::move(durationFunc)));
	DEBUG_LOG("set generator done" << std::endl);
	info.GetReturnValue().SetUndefined();
}

void NodeSimulator::GetServerStatus(const Nan::FunctionCallbackInfo<v8::Value>& info) {
	NodeSimulator* obj = ObjectWrap::Unwrap<NodeSimulator>(info.Holder());
	AutoScale::ServerStatus status = obj->simulator_.GetServerStatus();
	v8::Local<v8::Object> statusObj = Nan::New<v8::Object>();
	statusObj->Set(Nan::New<v8::String>("instanceCount").ToLocalChecked(), Nan::New<v8::Integer>(status.InstanceCount));
	statusObj->Set(Nan::New<v8::String>("totalRequests").ToLocalChecked(), Nan::New<v8::Integer>(status.TotalRequests));
	statusObj->Set(Nan::New<v8::String>("loadPercentage").ToLocalChecked(), Nan::New<v8::Integer>(status.LoadPercentage()));
	info.GetReturnValue().Set(statusObj);
}

void NodeSimulator::GetLoadStatus(const Nan::FunctionCallbackInfo<v8::Value>& info) {
	NodeSimulator* obj = ObjectWrap::Unwrap<NodeSimulator>(info.Holder());
	AutoScale::LoadStatus status = obj->simulator_.GetRecentLoadStatus();
	v8::Local<v8::Object> statusObj = Nan::New<v8::Object>();
	statusObj->Set(Nan::New<v8::String>("requestComing").ToLocalChecked(), Nan::New<v8::Integer>(status.RequestComing));
	statusObj->Set(Nan::New<v8::String>("requestComplete").ToLocalChecked(), Nan::New<v8::Integer>(status.RequestComplete));
	info.GetReturnValue().Set(statusObj);
}

void NodeSimulator::AddRule(const Nan::FunctionCallbackInfo<v8::Value>& info) {
	if (info.Length() < 1 || !info[0]->IsObject())
	{
		Nan::ThrowError("Argument error");
		info.GetReturnValue().SetUndefined();
		return;
	}
	DEBUG_LOG("add rule" << std::endl);
	NodeSimulator* obj = ObjectWrap::Unwrap<NodeSimulator>(info.Holder());
	auto opts = info[0].As<v8::Object>();
	int by = opts->Get(Nan::New<v8::String>("by").ToLocalChecked())->Int32Value();
	AutoScale::RuleOpcode opcode = static_cast<AutoScale::RuleOpcode>(opts->Get(Nan::New<v8::String>("opcode").ToLocalChecked())->Int32Value());
	int threshold = opts->Get(Nan::New<v8::String>("threshold").ToLocalChecked())->Int32Value();
	int interval = opts->Get(Nan::New<v8::String>("interval").ToLocalChecked())->Int32Value();
	DEBUG_LOG("rule,by:" << by << ", opcode:" << opcode << ", thres:" << threshold << ", interval:" << interval << std::endl);
	AutoScale::ScaleType scaleType;
	if (by == 0) {
		scaleType = AutoScale::ScaleByCount;
	}
	else if (by == 1)
	{
		scaleType = AutoScale::ScaleByPercent;
	}
	else
	{
		Nan::ThrowError("Argument error");
		info.GetReturnValue().SetUndefined();
		return;
	}
	obj->simulator_.AddRule(AutoScale::ScaleRule::Make(scaleType, opts->Get(Nan::New<v8::String>("value").ToLocalChecked())->Int32Value(), opcode, threshold), interval);
	DEBUG_LOG("rule added" << std::endl);
	info.GetReturnValue().SetUndefined();
}

void InitAll(v8::Local<v8::Object> target) {
	NodeSimulator::Init(target);
}

NODE_MODULE(AutoScaleSimulator, InitAll);